﻿using System;
using System.Collections.Generic;
using System.Linq;
using Habanero.Base;

namespace FireStarter.Base.BusinessObjectMerger
{
    public class MergeableBORegistry
    {
        private static readonly MergeableBORegistry _boTestMergeableBORegistry = new MergeableBORegistry();
        /// <summary>
        /// Returns the Singleton Registry Instance
        /// </summary>
        public static MergeableBORegistry Instance
        {
            get
            {
                return _boTestMergeableBORegistry;
            }
        }
        private readonly Dictionary<Type, Type> _boTestMergeableTypes = new Dictionary<Type, Type>();

        public bool Contains<TBO>()
        {
            return _boTestMergeableTypes.ContainsKey(typeof (TBO));
        }

        /// <summary>
        /// Registeres a specific type <typeparamref name="TMergeBO"/> of <see cref="IMergeBO{T}"/> for the specified 
        /// <see cref="IBusinessObject"/> <typeparamref name="TBO"/>
        /// </summary>
        /// <typeparam name="TBO"></typeparam>
        /// <typeparam name="TMergeBO"></typeparam>
        public virtual void Register<TBO, TMergeBO>()
            where TBO : class, IMergableBo
            where TMergeBO : IMergeBO<TBO>
        {
            this.Register(typeof(TBO), typeof(TMergeBO));
        }

        private void Register(Type boType, Type factoryType)
        {
//            ValidateTypes(boType, factoryType);
            this.ClearPreviousInstances(boType);
            this._boTestMergeableTypes.Add(boType, factoryType);
        }
        /// <summary>
        /// Resolves the registered  <see cref="IMergeBO{TBO}"/>
        /// </summary>
        /// <typeparam name="TBO"></typeparam>
        /// <returns></returns>
        public virtual IMergeBO<TBO> Resolve<TBO>() where TBO : class, IMergableBo
        {
            Type typeOfBO = typeof(TBO);
            return (IMergeBO<TBO>) this.Resolve(typeOfBO);
        }

        public virtual IMergeBO Resolve(Type typeOfBO)
        {
            Type mergeBOType;
            if (this._boTestMergeableTypes.ContainsKey(typeOfBO))
            {
                mergeBOType = this._boTestMergeableTypes[typeOfBO];
                return (IMergeBO)Activator.CreateInstance(mergeBOType);
            }
            mergeBOType = typeof(MergeBO<>).MakeGenericType(new[] { typeOfBO });
            //Excludes Types created via RhinoMocks.
            AppDomainTypeSource typeSource = new AppDomainTypeSource(type => !type.Name.Contains("Proxy"));
            Type firstSubType = typeSource.GetTypes()
                .Where(mergeBOType.IsAssignableFrom)
                .FirstOrDefault();
            if (firstSubType != null)
            {
                mergeBOType = firstSubType;
            }
            return (IMergeBO)Activator.CreateInstance(mergeBOType);
        }

        private void ClearPreviousInstances(Type boType)
        {
            if (this._boTestMergeableTypes.ContainsKey(boType))
            {
                this._boTestMergeableTypes.Remove(boType);
            }
        }

        /// <summary>
        /// Gets the Types for the Currently Loaded App Domain
        /// </summary>
        private class AppDomainTypeSource
        {
            public AppDomainTypeSource(Func<Type, bool> where)
            {
                this.Where = where;
            }

            public IEnumerable<Type> GetTypes()
            {
                return ((this.Where == null) ? TypesImplementing() : TypesImplementing().Where(this.Where));
            }

            private static IEnumerable<Type> TypesImplementing()
            {
                return AppDomain.CurrentDomain.GetAssemblies()
                    .SelectMany(assembly1 => assembly1.GetTypes())
                    .Where(type1 => !type1.IsInterface && !type1.IsAbstract);
            }

            private Func<Type, bool> Where { get; set; }
        }
    }
}